package com.zhichat.service.impl;

import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.aliyun.oss.model.Callback;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.zhichat.dao.ChatSessionDao;
import com.zhichat.dao.ContactInfoDao;
import com.zhichat.dao.GroupInfoDao;
import com.zhichat.dao.UserInfoDao;
import com.zhichat.document.ChatMsgDoc;
import com.zhichat.entity.ChatSessionEntity;
import com.zhichat.entity.ContactInfoEntity;
import com.zhichat.entity.SysConfigEntity;
import com.zhichat.entity.UserInfoEntity;
import com.zhichat.infrastructure.Enums.ChatMsgStatusEnum;
import com.zhichat.infrastructure.Enums.ContactStatusEnum;
import com.zhichat.infrastructure.Enums.ContactTypeEnum;
import com.zhichat.infrastructure.Enums.MsgTypeEnum;
import com.zhichat.infrastructure.config.OSSConfig;
import com.zhichat.infrastructure.config.ZhiChatConfig;
import com.zhichat.infrastructure.constants.Constants;
import com.zhichat.infrastructure.exception.http.CommonException;
import com.zhichat.infrastructure.redis.Keys;
import com.zhichat.infrastructure.redis.RedisRepository;
import com.zhichat.infrastructure.redis.rediskey.Key;
import com.zhichat.infrastructure.redis.rediskey.ZhiChat;
import com.zhichat.infrastructure.util.OSSClient;
import com.zhichat.infrastructure.util.ThreadLocalCache;
import com.zhichat.infrastructure.util.Utils;
import com.zhichat.service.MessageService;
import com.zhichat.service.SysConfigService;
import com.zhichat.service.UserInfoService;
import com.zhichat.web.dto.MsgDTO;
import com.zhichat.web.dto.MsgUploadFileDTO;
import com.zhichat.web.socket.GlobalMsgHandle;
import com.zhichat.web.vo.ws.MsgVO;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Objects;

/**
 * @ClassName: MessageServiceImpl
 * @Description: 消息ServiceImpl
 * @Author: 团长九哥 https://space.bilibili.com/602197686
 * @CreateDate: 2024/12/18 10:12
 */
@Slf4j
@Service
@AllArgsConstructor
public class MessageServiceImpl implements MessageService {

    private final MongoTemplate mongoTemplate;
    private final RedisRepository redisRepository;
    private final ContactInfoDao contactInfoDao;
    private final ChatSessionDao chatSessionDao;
    private final GlobalMsgHandle globalMsgHandle;
    private final SysConfigService sysConfigService;
    private final OSSConfig ossConfig;
    private final UserInfoDao userInfoDao;
    private final GroupInfoDao groupInfoDao;
    private final ZhiChatConfig zhiChatConfig;
    private final UserInfoService userInfoService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public MsgVO<String> sendMsg(MsgDTO dto) {
        Long receiverId = dto.getReceiverId();
        String fileName = dto.getFileName();
        Long fileSize = dto.getFileSize();
        Integer fileType = dto.getFileType();
        ContactTypeEnum contactTypeEnum = ContactTypeEnum.getEnum(dto.getReceiverType());
        MsgTypeEnum checkMsgType = MsgTypeEnum.getMsgTypeEnum(dto.getMsgType());
        // 参数校验
        if (Objects.isNull(contactTypeEnum) || Objects.isNull(checkMsgType)
                || !ArrayUtil.contains(new int[]{MsgTypeEnum.MEDIA_FILE.getCode(), MsgTypeEnum.CHAT.getCode()}, checkMsgType.getCode())
                || (checkMsgType.equals(MsgTypeEnum.MEDIA_FILE) && (StrUtil.isEmpty(fileName) || (Objects.isNull(fileSize) || fileSize == 0)))) {
            throw CommonException.PARAMETER_COMMON_EXCEPTION;
        }
        // 校验文件大小
        if (checkMsgType.equals(MsgTypeEnum.MEDIA_FILE)) {
//            checkMediaFile(dto.getMediaFile(), dto.getMediaFileCover());
        } else {
            // 如果不是聊天消息，而是系统消息，则转换msg
            if (!Objects.equals(MsgTypeEnum.MEDIA_FILE, checkMsgType) && !Objects.equals(MsgTypeEnum.CHAT, checkMsgType)) {
                UserInfoEntity receiverUser = userInfoService.getById(receiverId);
                if (Objects.isNull(receiverUser)) {
                    throw CommonException.PARAMETER_COMMON_EXCEPTION;
                }
                dto.setMsg(StrUtil.format(receiverUser.getNickname()));
            }
        }
        Long userId = ThreadLocalCache.getUserId();
        boolean contactIsUser = Objects.equals(ContactTypeEnum.USER, contactTypeEnum);
        // 验证是否被拉黑、是否是好友
        Key key = new ZhiChat(contactIsUser ? Keys.CONTACT_USER : Keys.CONTACT_GROUP);
        List<Long> members = redisRepository.members(key, Long.class, userId);
        if (!members.contains(receiverId)) {
            if (contactIsUser) {
                LambdaQueryWrapper<ContactInfoEntity> checkIsContactQuery = Wrappers.lambdaQuery(ContactInfoEntity.class);
                checkIsContactQuery.select(ContactInfoEntity::getStatus)
                        .eq(ContactInfoEntity::getContactId, receiverId)
                        .eq(ContactInfoEntity::getUserId, userId);
                ContactInfoEntity checkIsContact = contactInfoDao.selectOne(checkIsContactQuery);
                if (Objects.nonNull(checkIsContact) && Objects.equals(ContactStatusEnum.BLACKLIST_FRIEND.getCode(), checkIsContact.getStatus())) {
                    throw new CommonException("已拉黑对方");
                }
            }
            throw new CommonException("未添加对方为好友");
        }
        long curTime = System.currentTimeMillis();
        MsgVO<String> resVo = new MsgVO<>();
        // 修改会话、保存消息
        ChatMsgDoc chatMsgDoc = msgSendCommonHandler(dto
                , userId
                , ThreadLocalCache.getUserToken().getNickname()
                , curTime
                , contactIsUser);
        SysConfigEntity sysConfigInfo = sysConfigService.getSysConfigInfo();
        // 如果是机器人聊天，AI机器人服务端就是本服务端（可以接入AI的openAPI）
        if (Objects.equals(sysConfigInfo.getRobotId(), receiverId)) {
            // 机器人聊天处理
            String aiMsg = "你好，我是机器人"; // 获取ai接口返回的消息
            dto.setReceiverId(userId);
            dto.setMsg(aiMsg);
            dto.setReceiverType(ContactTypeEnum.USER.getCode());
            dto.setMsgType(MsgTypeEnum.CHAT.getCode());
            chatMsgDoc = msgSendCommonHandler(dto
                    , sysConfigInfo.getRobotId()
                    , sysConfigInfo.getRobotNickname()
                    , curTime
                    , true);
        }
        BeanUtils.copyProperties(chatMsgDoc, resVo);
        resVo.setMsgId(chatMsgDoc.getId());
        globalMsgHandle.sendHandler(resVo);
        return resVo;
    }

    @Transactional(rollbackFor = Exception.class)
    public ChatMsgDoc msgSendCommonHandler(MsgDTO dto, Long senderId, String senderNickname, long curTime, boolean contactIsUser) {
        Long receiverId = dto.getReceiverId();
        String msg = dto.getMsg();
        Integer receiverType = dto.getReceiverType();
        Integer msgType = dto.getMsgType();
        boolean isMedia = Objects.equals(MsgTypeEnum.MEDIA_FILE.getCode(), msgType);
        String sessionId = Utils.lsMd5(receiverId, senderId);
        // 修改会话 -> mysql
        if (!isMedia) {
            ChatSessionEntity updateChatSession = new ChatSessionEntity()
                    .setId(sessionId)
                    .setLastReceiveTime(curTime)
                    .setLastMsg(contactIsUser ? msg : senderNickname.concat("：").concat(msg));
            if (1 != chatSessionDao.updateById(updateChatSession)) {
                throw CommonException.DB_MODIFY_COMMON_EXCEPTION;
            }
        }
        // 添加消息 -> mongo
        ChatMsgDoc chatMsgDoc = new ChatMsgDoc()
                .setId(Utils.uniqueId())
                .setSessionId(sessionId)
                .setSenderId(senderId)
                .setSenderNickname(senderNickname)
                .setReceiverId(receiverId)
                .setReceiverType(receiverType)
                .setMsg(msg)
                .setMsgType(msgType)
                .setStatus(isMedia ? ChatMsgStatusEnum.SENDING.getCode() : ChatMsgStatusEnum.SUCCESS.getCode())
                .setSendTime(curTime);
        if (isMedia) {
            chatMsgDoc.setFileName(dto.getFileName())
                    .setFileSize(dto.getFileSize())
                    .setFileType(dto.getFileType());
        }
        mongoTemplate.insert(chatMsgDoc);
        return chatMsgDoc;
    }

    @Override
//    @Transaction(rollbackFor = Exception.class) 没做事务处理
    public Boolean sendFileSucCallback(HttpServletRequest request) {
        String callbackData = request.getParameterMap().keySet().toArray()[0].toString();
        log.info("文件上传成功回调接口：{}", callbackData);
        String[] tmpArr = callbackData.split(",");
        String msgId = tmpArr[tmpArr.length - 1].split(":")[1];
        if (StrUtil.isBlank(msgId)) {
            log.error("msgId为空，回调失败");
            return false;
        }
        // 处理消息
        ChatMsgDoc msgDoc = mongoTemplate.findById(msgId, ChatMsgDoc.class);
        if (Objects.isNull(msgDoc)) {
            log.error("消息获取失败");
            return false;
        }
        msgDoc.setStatus(ChatMsgStatusEnum.SUCCESS.getCode());
        mongoTemplate.save(msgDoc);
        String sessionId = msgDoc.getSessionId();
        String senderNickname = msgDoc.getSenderNickname();
        String msg = msgDoc.getMsg();
        Integer receiverType = msgDoc.getReceiverType();
        // 修改会话
        ChatSessionEntity updateChatSession = new ChatSessionEntity()
                .setId(sessionId)
                .setLastReceiveTime(msgDoc.getSendTime()) // 这块时间，可能会有点问题
                .setLastMsg(1 == receiverType ? msg : senderNickname.concat("：").concat(msg));
        if (1 != chatSessionDao.updateById(updateChatSession)) {
            throw CommonException.DB_MODIFY_COMMON_EXCEPTION;
        }
        // 发送消息
        MsgVO<String> resVo = new MsgVO<>();
        BeanUtils.copyProperties(msgDoc, resVo);
        resVo.setMsgId(msgDoc.getId());
        globalMsgHandle.sendHandler(resVo);
        return true;
    }

    private void checkMediaFile(MultipartFile mediaFile) {
        String fileSuffix = Utils.getFileSuffix(mediaFile.getOriginalFilename());
        if (Objects.isNull(fileSuffix)) {
            throw CommonException.PARAMETER_COMMON_EXCEPTION;
        }
        SysConfigEntity sysConfigInfo = sysConfigService.getSysConfigInfo();
        // 校验文件大小
        if (ArrayUtil.contains(Constants.IMG_SUFFIX, fileSuffix.toLowerCase())
                && Utils.mb2ByteNumber(sysConfigInfo.getMaxImageFileSize()) < mediaFile.getSize()) { // 图片
            throw new CommonException("图片过大");
        } else if (ArrayUtil.contains(Constants.VIDEO_SUFFIX, fileSuffix.toLowerCase())
                && Utils.mb2ByteNumber(sysConfigInfo.getMaxVideoFileSize()) < mediaFile.getSize()) { // 视频
            throw new CommonException("视频过大");
        } else {
            if (Utils.gb2ByteNumber(sysConfigInfo.getMaxFileSize()) < mediaFile.getSize()) {
                throw new CommonException("文件过大");
            }
        }
    }

    @Override
    public boolean uploadFile(MsgUploadFileDTO dto) {
        MultipartFile file = dto.getFile();
        if (file.isEmpty()) {
            throw CommonException.PARAMETER_COMMON_EXCEPTION;
        }
        checkMediaFile(file); // 校验文件
        String msgId = dto.getMsgId();
        ChatMsgDoc chatMsgDoc = mongoTemplate.findById(msgId, ChatMsgDoc.class);
        if (Objects.isNull(chatMsgDoc)) {
            throw CommonException.PARAMETER_COMMON_EXCEPTION;
        }
        Long userId = ThreadLocalCache.getUserId();
        Long sendTime = chatMsgDoc.getSendTime();
        String time = DateTime.of(sendTime).toString("yyyyMM");
        // 异步，避免阻塞
//        ThreadPoolWorker.singleInstance().getExecutor().execute(() -> {
//            String serverFolder = Constants.MSG_FILE_FOLDER + "/" + userId + "/" + time;
//            // 上传cover
//            MultipartFile coverFile = dto.getCover();
//            if (Objects.nonNull(coverFile) && !coverFile.isEmpty()) {
//                checkMediaFile(coverFile); // 校验文件（理论上 cover 肯定比file要小）
//                OSSClient.uploadFile(coverFile, serverFolder, null, ossConfig, null);
//            }
//            // 上传文件（带回调）
//            Callback callback = OSSClient.getCallback(zhiChatConfig.getMediaFileCallbackUrl(), new String[]{"msgId"}, new String[]{msgId});
//            OSSClient.uploadFile(file, serverFolder, null, ossConfig, callback);
//        });
        String serverFolder = Constants.MSG_FILE_FOLDER + "/" + userId + "/" + time;
        // 上传cover
        MultipartFile coverFile = dto.getCover();
        if (Objects.nonNull(coverFile) && !coverFile.isEmpty()) {
            checkMediaFile(coverFile); // 校验文件（理论上 cover 肯定比file要小）
            OSSClient.uploadFile(coverFile, serverFolder, null, ossConfig, null);
        }
        // 上传文件（带回调）
        Callback callback = OSSClient.getCallback(zhiChatConfig.getMediaFileCallbackUrl(), new String[]{"msgId"}, new String[]{msgId});
        OSSClient.uploadFile(file, serverFolder, null, ossConfig, callback);
        return true;
    }

    @Override
    public List<String> downloadFileMsg(Long id, Integer type, Boolean isCover) {
        // 校验参数
        ContactTypeEnum contactTypeEnum = ContactTypeEnum.getEnum(type);
        if (Objects.isNull(contactTypeEnum)) {
            throw CommonException.PARAMETER_COMMON_EXCEPTION;
        }
        String folder = null;
        if (Objects.equals(contactTypeEnum, ContactTypeEnum.USER)) {
            folder = Constants.USER_AVATAR_OSS_FOLDER;
            if (Objects.nonNull(userInfoDao.selectById(id))) {
                throw CommonException.PARAMETER_COMMON_EXCEPTION;
            }
        } else {
            folder = Constants.GROUP_AVATAR_OSS_FOLDER;
            if (Objects.nonNull(groupInfoDao.selectById(id))) {
                throw CommonException.PARAMETER_COMMON_EXCEPTION;
            }
        }
        String fileName = isCover ? id.toString().concat(Constants.IMG_SUF_PNG) : id.toString().concat(Constants.IMG_SUF_JPG);
        // 获取文件临时下载链接
        List<String> fileTmpDownloadUrl = OSSClient.getFileTmpDownloadUrl(folder, fileName, ossConfig);
        return fileTmpDownloadUrl;
    }

    @Override
    public void downloadFileMsg1(String id, Integer type, Boolean isCover, HttpServletResponse response) {
        // 校验参数
        String folder = null;
        String fileName = null;
        if (Objects.equals(type, ContactTypeEnum.USER.getCode())) {
            folder = Constants.USER_AVATAR_OSS_FOLDER;
            if (Objects.isNull(userInfoDao.selectById(id))) {
                throw CommonException.PARAMETER_COMMON_EXCEPTION;
            }
            fileName = id.toString().concat(Constants.IMG_SUF_PNG);
        } else if (Objects.equals(type, ContactTypeEnum.GROUP.getCode())) {
            folder = Constants.GROUP_AVATAR_OSS_FOLDER;
            if (Objects.isNull(groupInfoDao.selectById(id))) {
                throw CommonException.PARAMETER_COMMON_EXCEPTION;
            }
            fileName = id.toString().concat(Constants.IMG_SUF_PNG);
        } else if (Objects.equals(type, 3)) {
            ChatMsgDoc chatMsgDoc = mongoTemplate.findById(id, ChatMsgDoc.class);
            if (Objects.isNull(chatMsgDoc) || !Objects.equals(MsgTypeEnum.MEDIA_FILE.getCode(), chatMsgDoc.getMsgType())) {
                throw CommonException.PARAMETER_COMMON_EXCEPTION;
            }
            folder = Constants.MSG_FILE_FOLDER + "/" + chatMsgDoc.getSenderId() + "/"
                    + DateTime.of(chatMsgDoc.getSendTime()).toString("yyyyMM");

            fileName = id + "." + Utils.getFileSuffix(chatMsgDoc.getFileName());
        }
        if (Objects.nonNull(isCover) && isCover) {
            fileName = fileName + Constants.COVER_SUF_JPG;
        }
        // 设置 response 参数
//        response.setContentType("application/octet-stream");
        response.setContentType("application/x-msdownload;charset=UTF-8");
        response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
        // 流式下载文件
        OSSClient.downloadFile2Stream(folder, fileName, ossConfig, response);
    }

    @Override
    public void downloadMsgFileMsg(String id, Boolean isCover, HttpServletResponse response) {
        Long userId = ThreadLocalCache.getUserId();
        Key key = new ZhiChat(Keys.CONTACT_GROUP);
        List<Long> groupMembers = redisRepository.members(key, Long.class, userId);
        // 校验参数
        ChatMsgDoc chatMsgDoc = mongoTemplate.findById(id, ChatMsgDoc.class);
        if (Objects.isNull(chatMsgDoc) || !Objects.equals(MsgTypeEnum.MEDIA_FILE.getCode(), chatMsgDoc.getMsgType())) {
            throw CommonException.PARAMETER_COMMON_EXCEPTION;
        }
        if (!ArrayUtil.contains(ArrayUtil.toArray(groupMembers, Long.class), chatMsgDoc.getReceiverId())
                || !ArrayUtil.contains(new long[]{chatMsgDoc.getSenderId(), chatMsgDoc.getReceiverId()}, userId)) {
            throw CommonException.PARAMETER_COMMON_EXCEPTION;
        }
        String fileName = isCover ? chatMsgDoc.getFileName().concat(Constants.COVER_SUF_JPG) : chatMsgDoc.getFileName();
        String dateStr = DateUtil.date(chatMsgDoc.getSendTime()).toDateStr();
        String folder = Constants.MSG_FILE_FOLDER.concat("/").concat(dateStr);
        response.setContentType("application/x-msdownload;charset=UTF-8");
        response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
        // 流式下载文件
        OSSClient.downloadFile2Stream(folder, fileName, ossConfig, response);
    }
}
